declare namespace r='http://rules.independentsi.org';
declare namespace sx="http://saxon.sf.net/";
declare namespace xsl="http://www.w3.org/1999/XSL/Transform";

declare variable $rules_file external;

(:(),(<input>{sx:serialize($data, <xsl:output method="xml" indent="yes"/>)}</input>):)
declare function r:do-preprocess-steps($data as node()*, $preprocs as item()*) as node()*
{
	let $result := if(count($preprocs) > 1)
						then if($preprocs[1]='xslt')
								then sx:transform($preprocs[2], document {$data})
								else sx:query($preprocs[2], $data)
						else $data
	return if(count($preprocs) > 2)
				then r:do-preprocess-steps($result, subsequence($preprocs, 3))
				else $result
};

declare function r:get-base-path($rules as item()) as xs:string
{
	let $tokens := tokenize(document-uri($rules), '/')
	return string-join(reverse(subsequence(reverse($tokens), 2)), '/')
};

declare function r:get-includes($includes as item()*, $base as xs:string) as item()*
{
	let $nodes := for $inc in $includes
						return if(matches($inc/@path, '^/.+|^.:.+'))
							then doc($inc/@path)/*
							else doc(concat($base,'/',$inc/@path))/*
	
	return if(count($nodes) > 0) then insert-before(r:get-includes($nodes/r:rules/r:include, $base), 1, $nodes)
					else ()
};

<r:Results>
{
	let $filewriter := sx:compile-stylesheet( document {
		<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="2.0" xmlns:xs="http://www.w3.org/2001/XMLSchema">
			<xsl:param name="name" as="xs:string"/>
			<xsl:param name="path" as="xs:string"/>
			<xsl:template match="/">
				<xsl:result-document href="{{concat($path, '/', $name, '.xml')}}">
					<xsl:copy-of select="."/>
				</xsl:result-document>
			</xsl:template>
		</xsl:stylesheet>
	})
	
	let $rules := doc(replace($rules_file,'\\','/'))
	let $cwd := r:get-base-path($rules)
	let $includes := r:get-includes($rules/r:rules/r:include, $cwd)
	let $rns := for $x in in-scope-prefixes($rules/*)
			return concat('declare namespace ', $x, ' = "',namespace-uri-for-prefix($x,$rules/node()), '";')
	(:let $irns := if(count($includes)>0) then for $x in in-scope-prefixes($includes/*)
																	where $x ne 'xml'
																	return concat('declare namespace ', $x, 'inc = "',namespace-uri-for-prefix($x,$includes), '";')
													else ()
	:)
	let $subquery := string-join($rns, '&#10;')
	(:let $subquery := concat('&#10;', $subquery, string-join($irns, '&#10;')):)
	let $subquery := concat('&#10;', $subquery, 'declare namespace sx="http://saxon.sf.net/";','&#10; ')
	let $subquery := concat($subquery, 'declare variable $src external; ','&#10; ')
	let $subquery := concat($subquery, 'declare variable $sourcepath external; ','&#10; ')
	let $subquery := concat($subquery, 'declare variable $outdir external; ', '&#10; ')
	let $subquery := concat($subquery, 'declare variable $rlf external; ','&#10; ')
	let $subquery := concat($subquery, 'declare variable $cwd external; ','&#10; ')
	let $subquery  := concat($subquery, "	
	declare function r:replace-vars($path as xs:string, $vars as xs:string*, $values as xs:string*)
	{
		let $result := if(count($vars) = 0)
							then $path
							else r:replace-vars(replace($path, concat('\', $vars[1]), $values[1]), subsequence($vars, 2),subsequence($values, 2))
		return $result
	};
	
declare function r:get-includes($includes as item()*, $base as xs:string) as item()*
{
	let $nodes := for $inc in $includes
						return if(matches($inc/@path, '^/.+|^.:.+'))
							then doc($inc/@path)/*
							else doc(concat($base,'/',$inc/@path))/*
	
	return if(count($nodes) > 0) then insert-before(r:get-includes($nodes/r:rules/r:include, $base), 1, $nodes)
					else ()
};
	
	declare function r:evaluate-rules ( $ruleNode as node()*, $srcDoc as node()*, $tgtDoc as node()*, $var as xs:string*, $varValue as xs:string*, $allMacros as item()*) as item()*
	{
		let $macros := $ruleNode/r:macro
		let $allMacros := insert-before($macros, 1, $allMacros)
		let $items := for $rule in $ruleNode/r:rule
							return if(string-length($rule/@when) gt 0)
									 then if($srcDoc/sx:evaluate(r:replace-vars($rule/@when,$var, $varValue)))
											then r:evaluate-rule($rule, $srcDoc, $tgtDoc, $var, $varValue, $allMacros)
										   else ()
									 else r:evaluate-rule($rule, $srcDoc, $tgtDoc, $var, $varValue, $allMacros)
									 
		let $scoped := for $scope in $ruleNode/r:scope
								return r:evaluate-rules ($scope, $srcDoc, $tgtDoc/sx:evaluate(r:replace-vars($scope/@context, $var, $varValue)), $var, $varValue, $allMacros)
		let $foreached := for $each in $ruleNode/r:for-each
								return r:iterate($each, $srcDoc, $tgtDoc, $var, $varValue, $allMacros)
		let $whened := for $when in $ruleNode/r:when
								return if (true()=$srcDoc/sx:evaluate(r:replace-vars($when/@condition,$var, $varValue)))
											then <r:because reason='{$when/@description}'>{r:evaluate-rules($when, $srcDoc, $tgtDoc, $var, $varValue, $allMacros)}</r:because>
											else ()
		return insert-before(insert-before(insert-before($whened, 0, $foreached), 0, $scoped), 0, $items)
	} ;
	
	declare function r:iterate( $scope as node()*, $srcDoc as node()*, $tgtDoc as node()*, $vars as xs:string*, $values as xs:string*, $allMacros as item()*)
	{
		let $var := concat('$_', $scope/@var, '_')
		let $tgt := $tgtDoc/sx:evaluate($scope/@context)
		let $cnt := count($tgt)
		for $x in (1 to $cnt)
							return <r:iteration each='{$scope/@name}' index='{$x}'>{r:evaluate-rules($scope, $srcDoc, $tgt[$x], insert-before($vars, 0, $var) , insert-before($values, 0, string($x)), $allMacros)}</r:iteration>
	} ;

	declare function r:evaluate-rule( $rule as node(), $srcDoc as node()*, $tgtDoc as node()*, $var as xs:string*, $varValue as xs:string*, $allMacros as item()*) as item()
	{
		let $srcPath := if (starts-with($rule/@srcPath, 'macro:'))
								 then r:resolve-macro(r:replace-vars($rule/@srcPath, $var, $varValue), $allMacros)
								 else r:replace-vars($rule/@srcPath, $var, $varValue)
		let $tgtPath := if(starts-with($rule/@tgtPath,'macro:'))
								then r:resolve-macro(r:replace-vars($rule/@tgtPath, $var, $varValue), $allMacros)
								else r:replace-vars($rule/@tgtPath, $var, $varValue)
		
		return if($rule/@type='map')
			then r:evaluate-map($rule, $srcDoc, $tgtDoc, $tgtPath, $srcPath)
			else
				if($rule/@type='assert')
				then <r:assertion name='{concat($rule/@name, ' Assertion')}'>{$tgtDoc/sx:evaluate($tgtPath)}</r:assertion>
				else <r:warning name='{concat($rule/@name, ' Assertion')}'>{concat('ERROR ', $rule/@type, ' Unknown Rule Type')}</r:warning>
	};
	
	declare function r:evaluate-map($rule as node(), $srcDoc as node()*, $tgtDoc as node()*, $tgtPath as xs:string, $srcPath as xs:string) as item()
	{
		let $src := $srcDoc/sx:evaluate($srcPath)
		let $tgt := $tgtDoc/sx:evaluate($tgtPath)
		let $evalFunc := $rule/@mode
		let $name := concat($rule/@name, ' Mapping')
		let $result := if(string-length($src) = 0)
				then <r:mapping name='{$name}'>Unable to execute - no input data</r:mapping>
				else  if(r:evaluate-map($src, $tgt, $evalFunc))
						then <r:mapping name='{$name}'>true</r:mapping>
						else <r:mapping name='{$name}' sourceValue='{$src}' targetValue='{$tgt}'>false</r:mapping>
		return $result
	};
	
		(:
		
		:)
	declare function r:evaluate-map($src as xs:string*, $tgt as xs:string*, $func as xs:string?) as xs:boolean
	{
		let $r := if(string-length($func)>0)
					then sx:evaluate(replace(replace($func, '\$tgt', concat('&amp;apos;', $tgt[1], '&amp;apos;')), '\$src', concat('&amp;apos;', $src[1], '&amp;apos;')))
					else $src=$tgt
		return $r
	};
	
	declare function r:resolve-macro($params as xs:string, $macros as item()*) as xs:string
	{
		let $mname := substring-before(substring-after($params, ':'), '(')
		let $values := tokenize(substring-before(substring-after($params, '('), ')'), ',')
		let $macro := for $m in $macros
							where $m/@name=$mname
							return $m/@definition
		return r:replace-param($macro, $values, 1)
	};
	
	declare function r:replace-param($str as xs:string, $values as xs:string*, $idx as xs:integer)
	{
		let $result := if (count($values) > $idx)
				 then replace(r:replace-param($str, $values, $idx+1), concat('\$_', string($idx), '_'), normalize-space(string($values[$idx])))
				 else replace($str, concat('\$_', string($idx), '_'), normalize-space(string($values[$idx])))
		return $result
	};
	"
	)
	
	let $subquery := concat($subquery, "<r:ResultSet sourcepath='{$sourcepath}' source='{tokenize($src, '/')[last()]}' output='{$outdir}'>
	{
		let $tgtDoc := .
		let $srcDoc:= doc($src)
		let $rules := doc($rlf)
		let $includes := r:get-includes($rules/r:rules/r:include, $cwd)
		let $incres := for $incrn in $includes
								return r:evaluate-rules ($incrn, $srcDoc, $tgtDoc, (), (), ())
		let $ret := r:evaluate-rules ($rules/*, $srcDoc, $tgtDoc, (), (), ())
		return insert-before($incres, 1, $ret)
	}
	</r:ResultSet>")
	let $sq := sx:compile-query($subquery)
	
	let $src := if(matches($rules/r:rules/@for,'^/.+|^.:.+'))
						then doc($rules/r:rules/@for)
						else doc(concat($cwd, '/', $rules/r:rules/@for))
	
	let $xsl := sx:compile-stylesheet($src)
	
	
	let $preprocess := for $step in $rules/r:rules/r:preprocess/r:step
								return if($step/@type='xslt')
										 then ('xslt', sx:compile-stylesheet(doc($step/r:xslt/@path)))
										 else if($step/@type='xquery')
												 then ('xquery', sx:compile-query($step/r:xquery/text()))
												 else ()
	return
	<r:Meta xsltName="{$rules/r:rules/@for}" date="{current-dateTime()}">
	{
		for $src in collection(concat($cwd, '/',$rules/r:rules/@inputpath, '?select=', $rules/r:rules/@inputfilepattern))
			let $inter := document {
										r:do-preprocess-steps($src, $preprocess)
											}
			let $xformResult := sx:transform($xsl, $inter)
			
			let $outdoc := document {$xformResult }
			
			return sx:query($sq, $xformResult, (<src>{document-uri($src)}</src>,<sourcepath>{string($rules/r:rules/@inputpath)}</sourcepath>, <rlf>{$rules_file}</rlf>, <outdir>{string($rules/r:rules/@outpath)}</outdir>,<cwd>{$cwd}</cwd>, <x>{sx:transform($filewriter, $outdoc , (<name>{concat(tokenize(document-uri($src),'/')[last()], '.output')}</name>, <path>{concat($cwd, '/', $rules/r:rules/@outpath)}</path>))}</x>))
	}
	</r:Meta>
}
</r:Results>
